#pragma once
#include "md5_mesh_parser.h"
#include "md5_nodes.h"
#include "vertex.h"
#include "matrix.h"

#include <vector>

namespace krryn{
namespace scene{
	template<typename _RenderTag, typename _Vertex = scene::vertex>
	class model{
	public:
		// Quick and dirty
		std::vector<scene::mesh<_RenderTag, _Vertex>*> m_Meshes;
	};
}
namespace data{
	template<typename _RenderTag, typename _Vertex = scene::vertex>
	class md5_builder{
	public:
		typedef scene::index_buffer<_RenderTag> ib_type;
		typedef typename scene::index_buffer<_RenderTag>::ptr ib_ptr;

		typedef scene::vertex_buffer<_RenderTag, _Vertex> vb_type;
		typedef typename scene::vertex_buffer<_RenderTag, _Vertex>::ptr vb_ptr;

		typedef system::renderer<_RenderTag> render_type;

		md5_builder(const std::string &m_MeshFile) : m_MeshParser(m_MeshFile){
			try{
				m_MeshParser.parse();
			}catch(md5_parse_error &a_Error){
				std::cout << a_Error.what() << std::endl;
			}
		}

		scene::model<_RenderTag, _Vertex> build_model(render_type &a_Renderer){
			md5_model &l_Model = m_MeshParser.get_model();
			
			scene::model<_RenderTag, _Vertex> l_Result;

			for(int n = 0; n < l_Model.m_NumMeshes; n++){
				md5_mesh &l_Mesh = l_Model.m_Meshes[n];
				
				ib_ptr l_IndexBuffer = build_index_buffer(a_Renderer, l_Mesh);
				vb_ptr l_VertexBuffer = build_vertex_buffer(a_Renderer, l_Mesh, l_Model.m_BindingPose);

				l_Result.m_Meshes.push_back(
					new scene::mesh<_RenderTag, _Vertex>(
						l_VertexBuffer, l_IndexBuffer));
			}

			return l_Result;
		}
	private:

		ib_ptr build_index_buffer(render_type &a_Renderer, md5_mesh &a_Mesh){
			ib_ptr l_IndexBuffer(
				new ib_type(a_Renderer, a_Mesh.m_NumTriangles * 3));

			hardware_locker l_Riaa(*l_IndexBuffer, write);

			for(int i = 0, k = 0; i < a_Mesh.m_NumTriangles; i++){
				for(int j = 0; j < 3; j++, k++){
					*l_IndexBuffer += a_Mesh.m_Triangles[i].m_Vertices[j];
				}
			}

			return l_IndexBuffer;
		}

		vb_ptr build_vertex_buffer(render_type &a_Renderer, md5_mesh &a_Mesh, md5_joint *a_Skeleton){
			vb_ptr l_VertexBuffer(
				new vb_type(a_Renderer, a_Mesh.m_NumVertices));

			hardware_locker l_Riaa(*l_VertexBuffer, write);

			math::matrix l_CoordinateCorrection;
			l_CoordinateCorrection.rotate_x(270);

			for(int i = 0; i < a_Mesh.m_NumVertices; i++){
				math::vector3f l_Position = math::vector3f::zero();

				for(int j = 0; j < a_Mesh.m_Vertices[i].m_WeightCount; j++){
					int l_Offset = a_Mesh.m_Vertices[i].m_WeightStart;

					md5_weight &l_Weight = a_Mesh.m_Weights[l_Offset + j];
					md5_joint &l_Joint   = a_Skeleton[l_Weight.m_Joint];

					math::vector3f l_Temp = 
						l_Joint.m_Rotation.rotate(l_Weight.m_Position);

					l_Position += 
						(l_Joint.m_Position + l_Temp) * l_Weight.m_Bias;
				}

				*l_VertexBuffer +=
					 scene::vertex(l_CoordinateCorrection.transform(l_Position))
					.uv(a_Mesh.m_Vertices[i].m_UV);
			}

			return l_VertexBuffer;
		}

		md5_mesh_parser m_MeshParser;
	};
}
}